package com.wangjiang.foundation.common.util;

import com.wangjiang.foundation.common.exception.CommonException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate;
import wiremock.com.google.common.collect.Lists;
import wiremock.com.google.common.collect.Sets;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;

@Slf4j
public class ClassUtil extends cn.hutool.core.util.ClassUtil{

    /**
     * Suffix for array class names: "[]"
     */
    public static final String ARRAY_SUFFIX = "[]";
    /**
     * The CGLIB class separator: "$$"
     */
    public static final String CGLIB_CLASS_SEPARATOR = "$$";
    /**
     * The ".class" file suffix
     */
    public static final String CLASS_FILE_SUFFIX = ".class";
    /**
     * Prefix for internal array class names: "["
     */
    private static final String INTERNAL_ARRAY_PREFIX = "[";
    /**
     * Prefix for internal non-primitive array class names: "[L"
     */
    private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";
    /**
     * The package separator character: '.'
     */
    private static final char PACKAGE_SEPARATOR = '.';
    /**
     * The path separator character: '/'
     */
    private static final char PATH_SEPARATOR = '/';
    /**
     * The inner class separator character: '$'
     */
    private static final char INNER_CLASS_SEPARATOR = '$';

    /**
     * 获取一个类的注解,如果未获取到则获取父类
     *
     * @param clazz      要获取的类
     * @param annotation 注解类型
     * @param <T>        注解类型泛型
     * @return 注解
     */
    public static <T extends Annotation> T getAnnotation(Class<?> clazz, Class<T> annotation) {
        if (clazz == null) {
            return null;
        }
        T ann = clazz.getAnnotation(annotation);
        if (ann != null) {
            return ann;
        } else {
            if (clazz.getSuperclass() != Object.class) {
                //尝试获取父类
                return getAnnotation(clazz.getSuperclass(), annotation);
            }
        }
        return ann;
    }

    /**
     * 获取一个方法的注解,如果未获取则获取父类方法
     *
     * @param method     要获取的方法
     * @param annotation 注解类型
     * @param <T>        注解类型泛型
     * @return
     */
    public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotation) {
        T ann = method.getAnnotation(annotation);
        if (ann != null) {
            return ann;
        } else {
            Class clazz = method.getDeclaringClass();
            Class superClass = clazz.getSuperclass();
            if (superClass != null && superClass != Object.class) {
                try {
                    //父类方法
                    Method suMethod = superClass.getMethod(method.getName(), method.getParameterTypes());
                    return getAnnotation(suMethod, annotation);
                } catch (NoSuchMethodException e) {
                    return null;
                }
            }
        }
        return ann;
    }

    /**
     * See
     * for the logic of this method. The ordering here is slightly different in that we
     * consider method-specific annotations on an interface before class-level ones.
     */
    public static <A extends Annotation> A getAnnotation(Method method, Class<?> targetClass,
                                                         Class<A> annotationClass) {
        // The method may be on an interface, but we need attributes from the target
        // class.
        // If the target class is null, the method will be unchanged.
        Method specificMethod = getMostSpecificMethod(method, targetClass);
        A annotation = getAnnotation(specificMethod, annotationClass);

        if (annotation != null) {
            log.debug(annotation + " found on specific method: " + specificMethod);
            return annotation;
        }

        // Check the original (e.g. interface) method
        if (specificMethod != method) {
            annotation = getAnnotation(method, annotationClass);

            if (annotation != null) {
                log.debug(annotation + " found on: " + method);
                return annotation;
            }
        }

        // Check the class-level (note declaringClass, not targetClass, which may not
        // actually implement the method)
        annotation = getAnnotation(specificMethod.getDeclaringClass(), annotationClass);

        if (annotation != null) {
            log.debug(annotation + " found on: "
                    + specificMethod.getDeclaringClass().getName());
            return annotation;
        }

        return null;
    }

    /**
     * Given a method, which may come from an interface, and a target class used
     * in the current reflective invocation, find the corresponding target method
     * if there is one. E.g. the method may be {@code IFoo.bar()} and the
     * target class may be {@code DefaultFoo}. In this case, the method may be
     * {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
     * this method does <i>not</i> resolve Java 5 bridge methods automatically.
     * if bridge method resolution is desirable (e.g. for obtaining metadata from
     * the original method definition).
     * <p><b>NOTE:</b> Since Spring 3.1.1, if Java security settings disallow reflective
     * access (e.g. calls to {@code Class#getDeclaredMethods} etc, this implementation
     * will fall back to returning the originally provided method.
     *
     * @param method      the method to be invoked, which may come from an interface
     * @param targetClass the target class for the current invocation.
     *                    May be {@code null} or may not even implement the method.
     * @return the specific target method, or the original method if the
     * {@code targetClass} doesn't implement it or is {@code null}
     */
    public static Method getMostSpecificMethod(Method method, Class<?> targetClass) {
        if (method != null && isOverridable(method, targetClass) &&
                targetClass != null && targetClass != method.getDeclaringClass()) {
            try {
                if (Modifier.isPublic(method.getModifiers())) {
                    try {
                        return targetClass.getMethod(method.getName(), method.getParameterTypes());
                    } catch (NoSuchMethodException ex) {
                        return method;
                    }
                } else {
                    Method specificMethod =
                            ReflectionUtil.getAccessibleMethod(targetClass, method.getName(), method.getParameterTypes());
                    return (specificMethod != null ? specificMethod : method);
                }
            } catch (SecurityException ex) {
                // Security settings are disallowing reflective access; fall back to 'method' below.
            }
        }
        return method;
    }

    /**
     * Determine whether the given method is overridable in the given target class.
     *
     * @param method      the method to check
     * @param targetClass the target class to check against
     */
    private static boolean isOverridable(Method method, Class<?> targetClass) {
        if (Modifier.isPrivate(method.getModifiers())) {
            return false;
        }
        if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) {
            return true;
        }
        return getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass));
    }

    /**
     * Determine the name of the package of the given class,
     * e.g. "java.lang" for the {@code java.lang.String} class.
     *
     * @param clazz the class
     * @return the package name, or the empty String if the class
     * is defined in the default package
     */
    public static String getPackageName(Class<?> clazz) {
        Validate.notNull(clazz, "Class must not be null");
        return getPackageName(clazz.getName());
    }

    /**
     * Determine the name of the package of the given fully-qualified class name,
     * e.g. "java.lang" for the {@code java.lang.String} class name.
     *
     * @param fqClassName the fully-qualified class name
     * @return the package name, or the empty String if the class
     * is defined in the default package
     */
    public static String getPackageName(String fqClassName) {
        Validate.notNull(fqClassName, "Class name must not be null");
        int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
        return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
    }

    public static Class<?> getGenericTypeByType(ParameterizedType genType, int index) {
        Type[] params = genType.getActualTypeArguments();
        if (index >= params.length || index < 0) {
            return null;
        }
        Object res = params[index];
        if (res instanceof Class) {
            return ((Class) res);
        }
        if (res instanceof ParameterizedType) {
            return (Class<?>) ((ParameterizedType) res).getRawType();
        }
        return null;
    }

    /**
     * 获取一个类的泛型类型,如果未获取到返回Object.class
     *
     * @param clazz 要获取的类
     * @param index 泛型索引
     * @return 泛型
     */
    public static Class<?> getGenericType(Class clazz, int index) {
        List<Type> arrys = new ArrayList<>();
        arrys.add(clazz.getGenericSuperclass());
        arrys.addAll(Arrays.asList(clazz.getGenericInterfaces()));
        return arrys.stream()
                .filter(Objects::nonNull)
                .map(type -> {
                    if (clazz != Object.class && !(type instanceof ParameterizedType)) {
                        return getGenericType(clazz.getSuperclass(), index);
                    }
                    return getGenericTypeByType(((ParameterizedType) type), index);
                })
                .filter(Objects::nonNull)
                .filter(res -> res != Object.class)
                .findFirst()
                .orElse((Class) Object.class);

    }

    /**
     * 获取一个类的第一个泛型的类型
     *
     * @param clazz 要获取的类
     * @return 泛型
     */
    public static Class<?> getGenericType(Class clazz) {
        return getGenericType(clazz, 0);
    }


    public static boolean instanceOf(Class clazz, Class target) {
        if (clazz == null) {
            return false;
        }
        if (clazz == target) {
            return true;
        }
        if (target.isInterface()) {
            for (Class aClass : clazz.getInterfaces()) {
                if (aClass == target) {
                    return true;
                }
            }
        }
        if (clazz.getSuperclass() == target) {
            return true;
        } else {
            if (clazz.isInterface()) {
                for (Class aClass : clazz.getInterfaces()) {
                    if (instanceOf(aClass, target)) {
                        return true;
                    }
                }
            }
            return instanceOf(clazz.getSuperclass(), target);
        }
    }

    /**
     * 当前类是否是基础类型的包装类.
     *
     * @param clz 类
     * @return boolean
     */
    public static boolean isWrapClass(Class clz) {
        try {
            return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 当前类是否是基础类型.
     *
     * @param clz 类
     * @return boolean
     */
    public static boolean isPrimaryClass(Class clz) {
        return clz.isPrimitive();
    }

    /**
     * 当前类是否是基础类型或者其包装类.
     *
     * @param clz 类
     * @return boolean
     */
    public static boolean isPrimaryOrWrapClass(Class clz) {
        return isPrimaryClass(clz) || isWrapClass(clz);
    }

    public static boolean isInterfaceOrAbstractClass(Class clz) {
        return Modifier.isAbstract(clz.getModifiers()) || Modifier.isInterface(clz.getModifiers());
    }

    @SuppressWarnings("unchecked")
    public static <S extends Collection<T>, T> S createCollection(Class<S> type, int len) {
        if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            try {
                return (S) type.newInstance();
            } catch (Exception e) {
                // ignore
            }
        }
        if (List.class.isAssignableFrom(type)) {
            if (type.isAssignableFrom(ArrayList.class)) {
                return (S) Lists.newArrayListWithExpectedSize(len);
            }
            if (type.isAssignableFrom(LinkedList.class)) {
                return (S) Lists.newLinkedList();
            }
        } else if (Set.class.isAssignableFrom(type)) {
            if (type.isAssignableFrom(HashSet.class)) {
                return (S) Sets.newHashSetWithExpectedSize(len);
            }
            if (type.isAssignableFrom(LinkedHashSet.class)) {
                return (S) Sets.newLinkedHashSet();
            }
        }

        throw new CommonException(String.format("collection class[%s] not supported.", type.getName()));
    }

    @SuppressWarnings("unchecked")
    public static <S extends Collection<T>, T> S createCollection(Class<S> type) {
        if (List.class.isAssignableFrom(type)) {
            if (type.isAssignableFrom(LinkedList.class)) {
                return (S) Lists.newLinkedList();
            }
            if (type.isAssignableFrom(ArrayList.class)) {
                return (S) Lists.newArrayList();
            }
        } else if (Set.class.isAssignableFrom(type)) {
            if (type.isAssignableFrom(LinkedHashSet.class)) {
                return (S) Sets.newLinkedHashSet();
            }
            if (type.isAssignableFrom(HashSet.class)) {
                return (S) Sets.newHashSet();
            }
        }
        if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            try {
                return (S) type.newInstance();
            } catch (Exception e) {
                // ignore
            }
        }
        throw new CommonException(String.format("collection class[%s] not supported.", type.getName()));
    }

    public static <S extends Map> S createMap(Class<S> cl) {
        Map result = null;
        if (HashMap.class == cl) {
            result = new HashMap();
        } else if (Hashtable.class == cl) {
            result = new Hashtable();
        } else if (IdentityHashMap.class == cl) {
            result = new IdentityHashMap();
        } else if (LinkedHashMap.class == cl) {
            result = new LinkedHashMap();
        } else if (Properties.class == cl) {
            result = new Properties();
        } else if (TreeMap.class == cl) {
            result = new TreeMap();
        } else if (WeakHashMap.class == cl) {
            result = new WeakHashMap();
        } else if (ConcurrentHashMap.class == cl) {
            result = new ConcurrentHashMap();
        } else if (ConcurrentSkipListMap.class == cl) {
            result = new ConcurrentSkipListMap();
        } else {
            try {
                result = (Map) cl.newInstance();
            } catch (Exception e) { /* ignore */ }

            if (result == null) {
                try {
                    Constructor<?> constructor = cl.getConstructor(Map.class);
                    result = (Map) constructor.newInstance(Collections.EMPTY_MAP);
                } catch (Exception e) { /* ignore */ }
            }
        }

        if (result == null) {
            result = new HashMap<>();
        }

        return (S) result;
    }


}